package com.nulldev.util.concurrency.threadIt.v3;

import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.RandomUtil;
import com.nulldev.util.concurrency.threadIt.v3.executors.Lightning;
import com.nulldev.util.concurrency.threadIt.v3.executors.LimitThreadPool;
import com.nulldev.util.concurrency.threadIt.v3.extensions.Task;
import com.nulldev.util.concurrency.threadIt.v4.asyncTasks.AsyncTaskCallback;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutionPreference;
import com.nulldev.util.concurrency.threadIt.v4.emapi.IExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.cft.CompleteableFutureTask;
import com.nulldev.util.concurrency.threadIt.v4.emapi.cntft.ControllableFutureTask;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ft.FutureTask;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Returnable;
import com.nulldev.util.concurrency.threadIt.v4.executors.DATE;
import com.nulldev.util.concurrency.threadIt.v4.executors.LightningFJ;
import com.nulldev.util.concurrency.threadIt.v4.executors.PBTE;
import com.nulldev.util.concurrency.threadIt.v4.executors.bqde.BQDE;
import com.nulldev.util.io.FastIO.FastIO;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.manage.MarkedForRemoval;
import com.nulldev.util.manage.nullUtil;

/**
 * <b>Deprecation stage is active</b> <br>
 * Deprecation stages:
 * <ul>
 * <li>Mark code as deprecated (done)</li>
 * <li>Warn about usage of the class (current)</li>
 * <li>Remove most executors and features</li>
 * <li>Prevent usage of class</li>
 * </ul>
 * 
 * @since 2.x
 * @planned-deprecation 3.0 (threadIt! v4 beta end -> Replacement: EMAPI
 *                      [ExecutorManager API])
 * @planned-removal 3.1
 */
@Deprecated
@MarkedForRemoval(plannedRemovalWhen = "3.1", reason = "Replaced by threadIt! 4.0")
public class ThreadDispatcher implements IExecutorManager {
	private static final Logger log = LoggerFactory.getLogger();

	public static enum InitMethod {
		/**
		 * The following executors are used: <br>
		 * <ul>
		 * <li>PBTE 4 (must be enabled manually)</li>
		 * <li>DATE 4 (must be enabled manually)</li>
		 * <li>Lighting 3 (JVM >= 1.8)</li>
		 * <li>newCachedThreadPool (JVM <= 1.7)</li>
		 * </ul>
		 */
		USE_MODERN_SELECTOR,
		USE_LEGACY_V2_V1_SELECTOR,
	}

	static {
		log.warn("[threadIt!] V3 has been deprecated and marked for removal, please upgrade to v4 soon.");
		new Exception().printStackTrace();
	}

	public static int randomID() {
		return RandomUtil.randInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
	}

	private static volatile ThreadDispatcher threadDispatcher;
	private ExecutorService executorService;
	// public ThreadsMonitor threadsMonitor = ThreadsMonitor.instance();

	public ThreadDispatcher() {
		this(false);
	}

	public ThreadDispatcher(final boolean preferLTQ) {
		this(preferLTQ, false);
	}

	public ThreadDispatcher(final boolean preferLTQ, final boolean avoidPBTE) {
		if (nullUtil.IS_ANDROID_BUILD)
			throw new UnsupportedOperationException("threadIt! v3 is not supported in the Android build. You should use v4 instead.");
		if (Arguments.hasArgument("--threadit3.usePBTE4") && !avoidPBTE) {
			this.executorService = new PBTE();
		} else if (Arguments.hasArgument("--threadit3.useLightningFJ")) {
			this.executorService = new LightningFJ();
		} else if (Arguments.hasArgument("--threadit3.useDATE4")) {
			this.executorService = new DATE(48); // EXPERIMENTAL
		} else if (Arguments.hasArgument("--threadit3.useDATE4_constricted")) {
			this.executorService = new DATE(4); // EXPERIMENTAL
		} else if (JVM.version() > 1.7) {
			if (!preferLTQ) {
				this.executorService = Executors.newCachedThreadPool(new ThreadFactory() {

					@Override
					public Thread newThread(Runnable r) {
						return new Thread(r, "Cached Thread Pool Thread");
					}
				});
			} else {
				this.executorService = new Lightning();
			}
		} else {
			log.warn("Outdated JRE detected, disabling Lighting...");
			this.executorService = Executors.newCachedThreadPool(new ThreadFactory() {

				@Override
				public Thread newThread(Runnable r) {
					return new Thread(r, "Cached Thread Pool Thread");
				}
			});
		}
	}

	public ThreadDispatcher(final int threadLimit, final boolean preferLTQ, final boolean avoidPBTE) {
		if (nullUtil.IS_ANDROID_BUILD)
			throw new UnsupportedOperationException("threadIt! v3 is not supported in the Android build. You should use v4 instead.");
		if (Arguments.hasArgument("--threadit3.usePBTE4") && !avoidPBTE) {
			this.executorService = new PBTE(threadLimit);
		} else if (Arguments.hasArgument("--threadit3.useLightningFJ")) {
			this.executorService = new LightningFJ(threadLimit);
		} else if (Arguments.hasArgument("--threadit3.useDATE4")) {
			this.executorService = new DATE(threadLimit); // EXPERIMENTAL
		} else if (Arguments.hasArgument("--threadit3.useDATE4_constricted")) {
			this.executorService = new DATE(threadLimit / 4); // EXPERIMENTAL
		} else if (JVM.version() > 1.7) {
			if (!preferLTQ) {
				this.executorService = Executors.newCachedThreadPool(new ThreadFactory() {

					@Override
					public Thread newThread(Runnable r) {
						return new Thread(r, "Cached Thread Pool Thread");
					}
				});
			} else {
				this.executorService = new Lightning(threadLimit);
			}
		} else {
			log.warn("Outdated JRE detected, disabling Lighting...");
			this.executorService = Executors.newCachedThreadPool(new ThreadFactory() {

				@Override
				public Thread newThread(Runnable r) {
					return new Thread(r, "Cached Thread Pool Thread");
				}
			});
		}
	}

	public ThreadDispatcher(final ExecutorService serv) {
		this.executorService = serv;
	}

	public ThreadDispatcher(final InitMethod method) {
		if (nullUtil.IS_ANDROID_BUILD)
			throw new UnsupportedOperationException("threadIt! v3 is not supported in the Android build. You should use v4 instead.");
		if (method == InitMethod.USE_MODERN_SELECTOR) {
			if (Arguments.hasArgument("--threadit3.usePBTE4")) {
				this.executorService = new PBTE();
			} else if (Arguments.hasArgument("--threadit3.useLightningFJ")) {
				this.executorService = new LightningFJ();
			} else if (Arguments.hasArgument("--threadit3.useDATE4")) {
				this.executorService = new DATE(48); // EXPERIMENTAL
			} else if (Arguments.hasArgument("--threadit3.useDATE4_constricted")) {
				this.executorService = new DATE(4); // EXPERIMENTAL
			} else if (JVM.version() > 1.7) {
				this.executorService = new Lightning();
			} else {
				log.warn("Outdated JRE detected, disabling Lighting...");
				this.executorService = Executors.newCachedThreadPool();
			}
		} else {
			if (JVM.version() > 1.7) {
				try {
					final Method v = Executors.class.getMethod("newWorkStealingPool");
					this.executorService = (ExecutorService) v.invoke(null, Runtime.getRuntime().availableProcessors());
				} catch (Exception e) {
					this.executorService = Executors.newCachedThreadPool();
				}
			} else {
				this.executorService = Executors.newCachedThreadPool();
			}
		}
	}

	public ThreadDispatcher(final InitMethod method, final int threadLimit) {
		if (nullUtil.IS_ANDROID_BUILD)
			throw new UnsupportedOperationException("threadIt! v3 is not supported in the Android build. You should use v4 instead.");
		if (threadLimit < 1)
			throw new UnsupportedOperationException("Invalid thread limit: " + threadLimit);
		if (method == InitMethod.USE_MODERN_SELECTOR) {
			if (Arguments.hasArgument("--threadit3.usePBTE4")) {
				this.executorService = new PBTE(threadLimit); // EXPERIMENTAL
			} else if (Arguments.hasArgument("--threadit3.useLightningFJ")) {
				this.executorService = new LightningFJ(threadLimit);
			} else if (Arguments.hasArgument("--threadit3.useDATE4")) {
				this.executorService = new DATE(threadLimit / 4); // EXPERIMENTAL
			} else if (Arguments.hasArgument("--threadit3.useDATE4_constricted")) {
				this.executorService = new DATE(threadLimit / 8); // EXPERIMENTAL
			} else if (JVM.version() > 1.7) {
				this.executorService = new Lightning(threadLimit);
			} else {
				log.warn("Outdated JRE detected, disabling Lighting...");
				this.executorService = Executors.newCachedThreadPool();
			}
		} else {
			if (JVM.version() > 1.7) {
				try {
					final Method v = Executors.class.getMethod("newWorkStealingPool");
					this.executorService = (ExecutorService) v.invoke(null, threadLimit);
				} catch (Exception e) {
					this.executorService = Executors.newCachedThreadPool();
				}
			} else {
				this.executorService = Executors.newCachedThreadPool();
			}
		}
	}

	public static ThreadDispatcher instance() {
		if (threadDispatcher == null && !nullUtil.IS_ANDROID_BUILD) {
			threadDispatcher = new ThreadDispatcher(Arguments.notArguments("--threadit3.disableLTQ"));
		}
		return threadDispatcher;
	}

	public void addTask(final FutureTask<?> task) {
		this.executorService.execute(task);
	}

	public void addTask(final CompleteableFutureTask<?> task) {
		this.executorService.execute(task);
	}

	@Override
	public void queueTask(final Runnable task) {
		this.executorService.execute(task);
	}

	@Override
	public void queueTask(final Runnable t, final ExecutionPreference e) {
		this.executorService.execute(t);
	}

	@Deprecated
	public void addTask(final Runnable task) {
		this.executorService.execute(task);
	}

	public void addTask(final Executable<?> task) {
		if (task instanceof Task<?>) {
			this.addTask((Task<?>) task);
		} else if (task instanceof FutureTask<?>) {
			this.addTask((FutureTask<?>) task);
		} else if (task instanceof CompleteableFutureTask<?>) {
			this.addTask((CompleteableFutureTask<?>) task);
		} else if (task instanceof ControllableFutureTask<?>) {
		} else {
			throw new UnsupportedOperationException("Cannot execute: " + task);
		}
		task.signalQueued();
	}

	public void addTask(final Task<?> task) {
		this.addTask(task, "$ThreadDispatcher.v3.1.Task");
	}

	public void addTask(final Task<?> task, final String name) {
		this.executorService.execute(new Runnable() {

			@Override
			public void run() {
				task.execute();
			}

			@Override
			public String toString() {
				return "Runnable[" + name + "]";
			}
		});
		task.signalQueued();
	}

	public ExecutorService getExecutor() {
		return this.executorService;
	}

	@Override
	public <T> Task<T> queueTask(final Task<T> t) {
		this.addTask(t);
		t.signalQueued();
		return t;
	}

	@Override
	public <T> FutureTask<T> queueTask(final FutureTask<T> t) {
		this.executorService.execute(t);
		t.signalQueued();
		return t;
	}

	@Override
	public <T> Executable<T> queueTask(final Executable<T> t) {
		this.addTask(t);
		t.signalQueued();
		return t;
	}

	@Override
	public <T> Executable<T> tryToAsyncSubmit(final Returnable<T> r) {
		/* UNSUPPORTED */
		return asyncSubmit(r);
	}

	@Override
	public <T> Executable<T> tryToSubmit(final Returnable<T> r) {
		/* UNSUPPORTED */
		return submit(r);
	}

	public static void main(String[] args) {
		args = ArrayUtils.add(args, "--Tasks.offloadAsync");
		Arguments.setup(args);
		/**
		 * Current State: <br />
		 * 1st place: Lightning (threadit3) <br />
		 * 2nd place: DATE-lightning (threadit4) <br />
		 * 3rd place: LimitThreadPool (threadit3 - technically threadit2-next) <br />
		 * 4th place: MBTE (threadit4) { REMOVED } <br />
		 * 5th place: PBTE (threadit4) [to fix: EXECUTION_MULTIPLE] <br />
		 * 6th place: LBTE (threadit4) [slow by design, although fixable in theory]
		 */
		JVM.println("threadit! v3.0: ThreadDispatcher demo.");
		final ThreadDispatcher td = new ThreadDispatcher(new BQDE(16, 4));
		JVM.println("Executor: " + td.getExecutor());
		JVM.println("=> Checking execution latency. [Task::single]");
		FastIO.get().flush();
		synchronized (td) {
			final long exec_lat_start = System.currentTimeMillis();
			final Task<Object> o = new Task<Object>() {

				@Override
				public Object run() {
					@SuppressWarnings("unused")
					final int ss = 13 + 65;
					final long exec_lat_stop = System.currentTimeMillis();
					FastIO.get().flush();
					JVM.println("Time taken: " + (exec_lat_stop - exec_lat_start) + " ms. [Task::execution]");
					return null;
				}

			};
			td.addTask(o);
		}
		JVM.println("=> Checking execution latency. [FutureTask::single]");
		FastIO.get().flush();
		synchronized (td) {
			final long exec_lat_start = System.currentTimeMillis();
			td.queueTask(new FutureTask<Object>() {

				@Override
				public Object execute() {
					@SuppressWarnings("unused")
					final int ss = 13 + 65;
					final long exec_lat_stop = System.currentTimeMillis();
					FastIO.get().flush();
					JVM.println("Time taken: " + (exec_lat_stop - exec_lat_start) + " ms. [FutureTask::execution]");
					return null;
				}
			});
		}
		JVM.println("=> Checking execution latency. [Task::multiple]");
		FastIO.get().flush();
		synchronized (td) {
			final long exec_lat_start = System.currentTimeMillis();
			for (int i = 0; i < 8; i++) {
				final int _i = i;
				final Task<Object> o = new Task<Object>() {

					@Override
					public Object run() {
						if (_i == 7) {
							final long exec_lat_stop = System.currentTimeMillis();
							FastIO.get().flush();
							JVM.println("Time taken: " + (exec_lat_stop - exec_lat_start) + " ms. [Task::execution-multiple]");
						}
						return null;
					}

				};
				td.addTask(o);
			}
		}
		JVM.println("=> Checking execution latency. [FutureTask::multiple]");
		FastIO.get().flush();
		synchronized (td) {
			final long exec_lat_start = System.currentTimeMillis();
			for (int i = 0; i < 8; i++) {
				final int _i = i;
				td.queueTask(new FutureTask<Object>() {

					@Override
					public Object execute() {
						if (_i == 7) {
							final long exec_lat_stop = System.currentTimeMillis();
							FastIO.get().flush();
							JVM.println("Time taken: " + (exec_lat_stop - exec_lat_start) + " ms. [FutureTask::execution-multiple]");
						}
						return null;
					}

				});
			}
		}
		JVM.println("=> Checking post-execution latency.");
		synchronized (td) {
			final long exec_post_lat_start = System.currentTimeMillis();
			final Task<Object> o1 = new Task<Object>() {

				@Override
				public Object run() {
					return 13 + 65;
				}

			};
			o1.addCallbackTask(new AsyncTaskCallback<Object>() {

				@Override
				public void onError(Throwable e) {
				}

				@Override
				public void onCompletion(Object result) {
					final long exec_lat_stop = System.currentTimeMillis();
					FastIO.get().flush();
					JVM.println("Time taken: " + (exec_lat_stop - exec_post_lat_start) + " ms. [Task::post-execution]");
				}
			});
			td.addTask(o1);
		}
		JVM.println("=> Checking post-execution latency.");
		synchronized (td) {
			final long exec_post_lat_start = System.currentTimeMillis();
			final FutureTask<Object> o1 = new FutureTask<Object>() {

				@Override
				public Object execute() {
					return 13 + 65;
				}

			};
			o1.addCallbackTask(new AsyncTaskCallback<Object>() {

				@Override
				public void onError(Throwable e) {
				}

				@Override
				public void onCompletion(Object result) {
					final long exec_lat_stop = System.currentTimeMillis();
					FastIO.get().flush();
					JVM.println("Time taken: " + (exec_lat_stop - exec_post_lat_start) + " ms. [FutureTask::post-execution]");
				}
			});
			td.queueTask(o1);
		}
		JVM.println("=> Checking waitTillCompletion latency.");
		synchronized (td) {
			final long exec_post_lat_start = System.currentTimeMillis();
			final Task<Object> o1 = new Task<Object>() {

				@Override
				public Object run() {
					return 14 + 22;
				}

			};
			td.addTask(o1);
			o1.waitTillCompletion();
			final long exec_lat_stop = System.currentTimeMillis();
			FastIO.get().flush();
			JVM.println("Time taken: " + (exec_lat_stop - exec_post_lat_start) + " ms. [Task::waitTillCompletion]");
		}
		JVM.println("=> Checking waitTillCompletion latency.");
		synchronized (td) {
			final long exec_post_lat_start = System.currentTimeMillis();
			final FutureTask<Object> o1 = td.queueTask(new FutureTask<Object>() {

				@Override
				public Object execute() {
					return 14 + 22;
				}

			});
			o1.waitTillCompletion();
			final long exec_lat_stop = System.currentTimeMillis();
			FastIO.get().flush();
			JVM.println("Time taken: " + (exec_lat_stop - exec_post_lat_start) + " ms. [FutureTask::waitTillCompletion]");
		}
		JVM.println("=> Checking dual-waiting latency.");
		synchronized (td) {
			final long exec_post_lat_start = System.currentTimeMillis();
			final Task<Object> o1 = new Task<Object>() {

				@Override
				public Object run() {
					return 14 + 22;
				}

			};
			td.addTask(o1);
			o1.waitTillCompletion();
			o1.addCallbackTask(new AsyncTaskCallback<Object>() {

				@Override
				public void onCompletion(Object result) {
					final long exec_lat_stop = System.currentTimeMillis();
					FastIO.get().flush();
					JVM.println("Time taken: " + (exec_lat_stop - exec_post_lat_start) + " ms. [Task::dual-wait]");
				}

				@Override
				public void onError(Throwable e) {
				}
			});

		}
		JVM.println("=> Checking dual-waiting latency.");
		synchronized (td) {
			final long exec_post_lat_start = System.currentTimeMillis();
			final FutureTask<Object> o1 = new FutureTask<Object>() {

				@Override
				public Object execute() {
					return 14 + 22;
				}

			};
			td.addTask(o1);
			o1.waitTillCompletion();
			o1.addCallbackTask(new AsyncTaskCallback<Object>() {

				@Override
				public void onCompletion(Object result) {
					final long exec_lat_stop = System.currentTimeMillis();
					FastIO.get().flush();
					JVM.println("Time taken: " + (exec_lat_stop - exec_post_lat_start) + " ms. [FutureTask::dual-wait]");
				}

				@Override
				public void onError(Throwable e) {
				}
			});

		}
	}

	@Override
	public void awaitIdle() {
		if (this.executorService instanceof Lightning) {
			((Lightning) this.executorService).awaitIdle();
		} else if (this.executorService instanceof DATE) {
			((DATE) this.executorService).awaitIdle();
		} else if (this.executorService instanceof PBTE) {
			((PBTE) this.executorService).awaitIdle();
		} else if (this.executorService instanceof LimitThreadPool) {
			((LimitThreadPool) this.executorService).awaitIdle();
		} else {
			throw new UnsupportedOperationException("Cannot wait for idle on executor: " + this.executorService);
		}
	}

	@Override
	public void await(final Executable<?>... _t) {
		for (final Executable<?> t : _t) {
			if (t != null) {
				t.waitTillCompletion();
			}
		}
	}

	@Override
	public void await(final List<Executable<?>> _t) {
		for (final Executable<?> t : _t) {
			if (t != null) {
				t.waitTillCompletion();
			}
		}
	}

	@Override
	public ExecutorService executor() {
		return this.executorService;
	}

	@Override
	public boolean isRunning() {
		return !this.executorService.isShutdown();
	}

}
